package com.nextbusmn.server;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Calendar;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TimeZone;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import javax.jdo.PersistenceManager;
import javax.jdo.Query;

import com.nextbusmn.server.JDOWrappers.BusRouteJDOWrapper;
import com.nextbusmn.server.JDOWrappers.BusStopJDOWrapper;
import com.nextbusmn.server.JDOWrappers.BusTripJDOWrapper;
import com.nextbusmn.server.JDOWrappers.CalendarDateJDOWrapper;
import com.nextbusmn.server.JDOWrappers.JDOWrapper;
import com.nextbusmn.server.JDOWrappers.ShapeJDOWrapper;
import com.nextbusmn.server.JDOWrappers.StopTimeJDOWrapper;
import com.nextbusmn.server.dataobjects.BusRouteData;
import com.nextbusmn.server.dataobjects.BusStopData;
import com.nextbusmn.server.dataobjects.BusTripData;
import com.nextbusmn.server.dataobjects.CalendarDateData;
import com.nextbusmn.server.dataobjects.ShapeData;
import com.nextbusmn.server.dataobjects.StopTimeData;


/**
 *  Utility class to parse contents of schedule files and persist them
 *  in the datastore.
 *  @author Trent Olson
 *  @author Simeon Gbolo
 */
public class ScheduleDataReader {
	
	// read bus route data
	public static void readRoutes(String routeDataString, String tripDataString, String stopTimeDataString) 
			throws IOException {

    // split data into arrays
		String[] routeLines = routeDataString.split("\n");
		String[] tripLines = tripDataString.split("\n");
		String[] stopTimeLines = stopTimeDataString.split("\n");
		
    // for each line in the routes file
		for (int i = 0; i < routeLines.length; ++i) {
			
		  // read each line, and split on commas
			String[] routeData = routeLines[i].split(",");
			
			// if the line has the correct number of entries
			if (routeData.length == 7) {
				try {
					// use data to create data object
					BusRouteData routeObj = new BusRouteData(
							// routeData[0] intentionally skipped
							// routeData[1] intentionally skipped 
							Integer.parseInt(routeData[2]),  			 // routeID
							routeData[3].replace("\"", ""));  			// routeLongName
							//routeData[4]-[6] intentionally skipped
          // set the bus trip data children
					routeObj.setBusTripData(getAllTrips(routeData[0], tripLines, stopTimeLines, routeObj));
					
					// wrap the object and insert in the datastore
					new BusRouteJDOWrapper(routeObj).insert();
					
				} catch (NumberFormatException e) {
					System.out.println("Skipping malformed data row: " +
							e.getMessage());
				}
			} else {
				System.out.println("Skipping malfomed data row: " +
						"Not enough fields");
			}
		 }
	}
	
  // get all bus trips for a given route
	private static Set<BusTripData> getAllTrips(String routeID, String[] tripLines, String[] stopTimeLines, 
			BusRouteData routeObj) throws IOException {

		Set<BusTripData> busTripData = new HashSet<BusTripData>();
    
    // for each line in the trips file
		for (int i = 0; i < tripLines.length; ++i) {
          // split the line on commas
					String[] tripData = tripLines[i].split(",");
               
          // if this trip belongs to the route we're processing
					if(tripData[0].equals(routeID)){
						// create a new busTripData object
						BusTripData dataTrips = new BusTripData(
								routeObj,
								Integer.parseInt(tripData[0].substring(0, 
									tripData[0].indexOf('-'))), 				// routeID
								getServiceType(tripData[1]),  				 // serviceType
								getTripID(tripData[2]), 							// tripID
								tripData[3].replace("\"", ""),  			 // tripHeadsign
								// tripData[4] intentionally skipped  
								Integer.parseInt(tripData[5].trim()));
            // set the stopTimes children of this trip
						dataTrips.setStopTimes(getStopAllStopTimes(tripData[2], stopTimeLines, dataTrips));
            // add this trip to the collection we're returning
						busTripData.add(dataTrips);
					}
				}
				
		return busTripData;
	}

  // get all the stop times for a given trip
	private static Set<StopTimeData> getStopAllStopTimes(String tripID,
			String[] stopTimeLines, BusTripData dataTrips) throws IOException  {

				Set<StopTimeData> stopTimeDat = new HashSet<StopTimeData>();
        
        // for each line in the stopTimes file
				for (int i = 0; i < stopTimeLines.length; ++i) {
              // read each line, and split on commas
							String[] stopTimeData = stopTimeLines[i].split(",");
              
              // if this stop time belongs to the trip we're processing
							if(stopTimeData[0].equals(tripID)){
								// create a new stop time object
								StopTimeData dataStops = new StopTimeData(
										getTripID(stopTimeData[0]),						   // tripID
										getDateFromTimeString(stopTimeData[1]),	  // arrivalTime
										getDateFromTimeString(stopTimeData[2]),	  // departureTime
										Integer.parseInt(stopTimeData[3]),				// stopID
										Integer.parseInt(stopTimeData[4]),        // stopSequence
										dataTrips);				
										// stopTimeData[5] - [6] intentionally skipped
								 // add this stop time to the collection we're returning
								stopTimeDat.add(dataStops);
							}
						}
						
				return stopTimeDat;	
	}

  // read all the stop data and persist stop objects
	public static void readStops(String stopDataString) throws IOException {
 
     // read each line, and split on commas
		String[] stopLines = stopDataString.split("\n");
		
		for (int i = 0; i < stopLines.length; ++i) {
			String[] stopData = stopLines[i].split(",");
			
			// if the line has the correct number of entries
			if (stopData.length == 12) {
				try {
					
					BusStopData busStopObj = new BusStopData(
							Integer.parseInt(stopData[0]),				// stopID
							stopData[1].replace("\"", ""),				// stopName
							stopData[2].replace("\"", ""),				// stopDesc
							Double.parseDouble(stopData[3]),			// stopLat
							Double.parseDouble(stopData[4]));			// stopLon
							//busStopData[5]-[11] intentionally skipped
					
					// wrap the object and insert in the datastore
					new BusStopJDOWrapper(busStopObj).insert();
					
				} catch (NumberFormatException e) {
					System.out.println("Skipping malformed data row: " +
							e.getMessage());
				}
			} else {
				System.out.println("Skipping malfomed data row: " +
						"Not enough fields");
			}
		}
	}
	
	// read and persist all shape data
	public static void readShapes(String shapeDataString) 
			throws IOException {
		
		String[] shapeLines = shapeDataString.split("\n");
		for (int i = 0; i < shapeLines.length; ++i) {
			String[] shapeData = shapeLines[i].split(",");
			
			if (shapeData.length == 4) {
				try {
					ShapeData shapeObj = new ShapeData(
							Integer.parseInt(shapeData[0].trim()),		  // shapeID
							Double.parseDouble(shapeData[1]),					// shapePtLat
							Double.parseDouble(shapeData[2]),					// shapePtLon
							Integer.parseInt(shapeData[3].trim()));
					new ShapeJDOWrapper(shapeObj).insert();
					System.out.println("Adding " + shapeData[0]);
					
				} catch (NumberFormatException e) {
					System.out.println("Skipping malformed data row: " +
							e.getMessage());
				}
			} else {
				System.out.println("Skipping malfomed data row: " +
						"Not enough fields");
			}
		}
	}
	
  // read and persist all calendar dates
	public static void readCalendarDates(String calDateDataString) 
			throws IOException {

		String[] calDateLines = calDateDataString.split("\n");
		for (int i = 0; i < calDateLines.length; ++i) {
			String[] calDateData = calDateLines[i].split(",");
			
			if (calDateData.length == 3) {
				try {
					CalendarDateData calDateObj = new CalendarDateData(
							calDateData[0],										         // serviceID
							getDateFromString(calDateData[1]),					// date
							Integer.parseInt(calDateData[2].trim()));
					new CalendarDateJDOWrapper(calDateObj).insert();
					System.out.println("Adding " + calDateData[0]);
					
				} catch (NumberFormatException e) {
					System.out.println("Skipping malformed data row: " +
							e.getMessage());
				}
			} else {
				System.out.println("Skipping malfomed data row: " +
						"Not enough fields");
			}
		}
	}
	
	// helper function to get a date from a time string to represent a time
	@SuppressWarnings("deprecation")	// Google's datastore can't persist Calendar objects
	private static Date getDateFromTimeString(String timeString) {
		
		int hour = Integer.parseInt(timeString.substring(0, 2));
		int minute = Integer.parseInt(timeString.substring(3,5));
		
		// get calendar object and set time zone
		Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("America/Chicago"));
		
		// "zero" out the fields we don't care about so we have a baseline date
		cal.set(Calendar.YEAR, 2000);
		cal.set(Calendar.MONTH, 6);			// but pick a month in CDT, not CST!
		cal.set(Calendar.DAY_OF_MONTH, 1);
		cal.set(Calendar.SECOND, 0);
		
		// set the parts of the time we care about
		cal.set(Calendar.HOUR_OF_DAY, hour);
		cal.set(Calendar.MINUTE, minute);
		return cal.getTime();
	}
	
  // helper function to read a date from a string
	@SuppressWarnings("deprecation")	// Google's datastore can't persist Calendar objects
	private static Date getDateFromString(String dateString) {
		
		// parse date from string
		int year = Integer.parseInt(dateString.substring(0, 4));
		int month = Integer.parseInt(dateString.substring(4, 6)) - 1;
		int day = Integer.parseInt(dateString.substring(6,8));
		
		// get calendar object and set time zone
		Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("America/Chicago"));
		
		// "zero" out the fields we don't care about so we have a baseline date
		cal.set(Calendar.SECOND, 0);
		cal.set(Calendar.HOUR_OF_DAY, 0);
		cal.set(Calendar.MINUTE, 0);
		
		// set the parts of the date we care about
		cal.set(Calendar.YEAR, year);
		cal.set(Calendar.MONTH, month);
		cal.set(Calendar.DAY_OF_MONTH, day);
		return cal.getTime();
	}
	
  // helper function to create a tripID that is unique & useful for us
	private static String getTripID(String rawTrip) throws StringIndexOutOfBoundsException {
		// 5143847-MAY12-Multi-Weekday-01
		StringBuilder tripID = new StringBuilder(rawTrip.substring(0, rawTrip.indexOf('-')));
		tripID.append(getServiceType(rawTrip));
		tripID.append(rawTrip.charAt(rawTrip.length() - 1));
		return tripID.toString();
	}
	
  // helper function to return an int to represent a service type 
	private static int getServiceType(String data) {
		int serviceType = 0;
		if (data.indexOf("Weekday") != -1) {
			serviceType = 0;
		} else if (data.indexOf("Saturday") != -1) {
			serviceType = 1;
		} else if (data.indexOf("Sunday") != -1) {
			serviceType = 2;
		} else if (data.indexOf("Holiday") != -1) {
			serviceType = 3;
		} else {
			throw new IllegalArgumentException("Invalid Service Type!");
		}
		return serviceType;
	}
}
